﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace MeshToolkit
{
    [RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
    public class RotatedBodyMesh : MonoBehaviour
    {
        public Vector3 mAlignPostion = Vector3.zero;   //要对齐到的坐标点（水平靠左、垂直居中）

        private Mesh mOrignalMesh = null;       //GameObject最原始的Mesh
        private Mesh mMesh = null;              //用于构造旋转体的Mesh（Z轴为常数的面片）
        private bool mIsOrignalMesh = false;    //GameObject的Mesh是否已经改变        

        /// <summary>
        /// 某条三角边
        /// </summary>
        private class LineVertexIndex
        {
            public int start;   //起点在顶点数组中的下标 
            public int end;     //重点在顶点数组中的下标
            public LineVertexIndex(int nStart, int nEnd)
            {
                start = nStart;
                end = nEnd;
            }
        }

        private List<LineVertexIndex> mEdgeLines = new List<LineVertexIndex>();  //组成外边框的三角边
        private Dictionary<int, int> mPointOnAlignAxis = new Dictionary<int, int>();                   //所有位于左侧对齐线上的顶点下标

        void Start()
        {
            if (GetComponent<MeshFilter>() != null)
            {
                mOrignalMesh = GetComponent<MeshFilter>().mesh;
                mMesh = mOrignalMesh;
                mIsOrignalMesh = true;
                InitEdgeLine(mMesh);
            }
        }

        private void AddOneLine(List<LineVertexIndex> oLines, Dictionary<string, List<int>> oIndexMap, int nStart, int nEnd)
        {
            oLines.Add(new LineVertexIndex(nStart, nEnd));
            string strKey = (nStart > nEnd) ? (nEnd.ToString() + "," + nStart.ToString()) : (nStart.ToString() + "," + nEnd.ToString());
            if (oIndexMap.ContainsKey(strKey))
            {
                oIndexMap[strKey].Add(oLines.Count - 1);
            }
            else
            {
                List<int> lstIndex = new List<int>();
                lstIndex.Add(oLines.Count - 1);
                oIndexMap.Add(strKey, lstIndex);
            }
        }

        /// <summary>
        /// 找出所有的外边界线（外边界线为所有三角面片中没有重复使用的边）
        /// </summary>
        /// <param name="oMesh"></param>
        private void InitEdgeLine(Mesh oMesh)
        {
            mEdgeLines.Clear();
            int[] lstTriangleIndex = oMesh.triangles;
            if (lstTriangleIndex.Length % 3 != 0)
            {
                throw new ArgumentNullException("Mesh's triangles invalid");
            }

            //统计所有三角面片的边对应的端点的ID，并将所有三角边保存到mEdgeLines
            Dictionary<string, List<int>> oIndexMap = new Dictionary<string, List<int>>(); 
            for (int i = 0; i < lstTriangleIndex.Length; i += 3)
            {
                int nIndexA = lstTriangleIndex[i];
                int nIndexB = lstTriangleIndex[i + 1];
                int nIndexC = lstTriangleIndex[i + 2];

                AddOneLine(mEdgeLines, oIndexMap, nIndexA, nIndexB);
                AddOneLine(mEdgeLines, oIndexMap, nIndexB, nIndexC);
                AddOneLine(mEdgeLines, oIndexMap, nIndexC, nIndexA);
            }

            //获取同时属于多个三角面片的三角边（位于面片内部的三角边）
            List<int> lstInnerLineIndex = new List<int>();
            foreach (var oMap in oIndexMap)
            {
                if (oMap.Value.Count > 1)
                {
                    lstInnerLineIndex.AddRange(oMap.Value);
                }
            }
            lstInnerLineIndex.Sort();

            //剔除内部三角边，只保留外边缘上的三角边
            for (int i = lstInnerLineIndex.Count - 1; i >= 0; i--)
            {
                mEdgeLines.RemoveAt(lstInnerLineIndex[i]);
            }
        }

        private void InitPointOnAlignAxis(List<Vector3> oVertices)
        {
            mPointOnAlignAxis.Clear();
            for (int i = 0; i < oVertices.Count; i++)
            {
                if (Math.Abs(oVertices[i].x) < 1e-6f && Math.Abs(oVertices[i].x) < 1e-6f)
                {
                    mPointOnAlignAxis.Add(i, -1);
                }
            }
        }

        /// <summary>
        /// 将Mesh按照垂直居中、水平靠左的方式对齐到mAlignPostion
        /// </summary>
        /// <param name="bChangeMesh">是否更改Mesh顶点数组</param>
        public void AlignMesh(bool bChangeMesh)
        {
            if (mOrignalMesh == null)
            {
                throw new ArgumentNullException("mOrignalMesh");
            }

            Vector2 oLeft, oRight;
            GetMeshBound(mOrignalMesh, transform.rotation, out oLeft, out oRight);
            if (bChangeMesh && mIsOrignalMesh)
            {                
                MeshDraft oDraft = new MeshDraft(mOrignalMesh);
                oDraft.Rotate(transform.rotation);
                oDraft.Move(mAlignPostion + new Vector3(-oLeft.x, -(oLeft.y + oRight.y) / 2, 0));

                //重设normal和uv
                oDraft.normals.Clear();
                oDraft.uv.Clear();
                oDraft.uv2.Clear();
                oDraft.uv3.Clear();
                oDraft.uv4.Clear();
                oDraft.tangents.Clear();

                var normal = Vector3.Cross(oDraft.vertices[oDraft.triangles[1]] - oDraft.vertices[oDraft.triangles[0]],
                    oDraft.vertices[oDraft.triangles[2]] - oDraft.vertices[oDraft.triangles[0]]).normalized;
                for (int i = 0; i < oDraft.vertices.Count; i++)
                {
                    oDraft.normals.Add(normal);
                    oDraft.uv.Add(new Vector2((float)i / oDraft.vertices.Count, (float)i / oDraft.vertices.Count));
                }
                mMesh = oDraft.ToMesh();                

                //将transform的position重置为原点，rotation重置为零
                transform.rotation = Quaternion.identity;
                transform.localRotation = Quaternion.identity;
                transform.localPosition = Vector3.zero;
                GetComponent<MeshFilter>().mesh = oDraft.ToMesh();
                mIsOrignalMesh = false;
            }
            else
            {
                transform.localPosition =  mAlignPostion + new Vector3(-oLeft.x, -(oLeft.y + oRight.y) / 2 , 0);
            }
        }

        /// <summary>
        /// 获取旋转后的面片Mesh（假设Z轴坐标均为0）的矩形包围框
        /// </summary>
        /// <param name="oMesh"> 图形的Mesh</param>
        /// <param name="oRotate">旋转角度(假设仅绕Z轴旋转)</param>
        /// <param name="oLeftDown">旋转后矩形包围框的左下角点坐标</param>
        /// <param name="oRightUp"> 旋转后矩形包围框的右上角点坐标</param>
        public static void GetMeshBound(Mesh oMesh, Quaternion oRotate, out Vector2 oLeftDown, out Vector2 oRightUp)
        {
            if (oMesh == null)
            {
                throw new ArgumentNullException("oMesh");
            }
            //将Mesh数据进行旋转
            MeshDraft oDraft = new MeshDraft(oMesh);
            oDraft.Rotate(oRotate);

            //计算旋转后的坐标范围
            float dMinX = float.MaxValue;
            float dMaxX = float.MinValue;
            float dMinY = float.MaxValue;
            float dMaxY = float.MinValue;
            for (int i = 0; i < oDraft.vertexCount; i++)
            {
                Vector3 oPoint = oDraft.vertices[i];
                if (oPoint.x < dMinX)
                {
                    dMinX = oPoint.x;
                }
                if (oPoint.x > dMaxX)
                {
                    dMaxX = oPoint.x;
                }
                if (oPoint.y < dMinY)
                {
                    dMinY = oPoint.y;
                }
                if (oPoint.y > dMaxY)
                {
                    dMaxY = oPoint.y;
                }
            }

            oLeftDown.x = dMinX;
            oLeftDown.y = dMinY;
            oRightUp.x = dMaxX;
            oRightUp.y = dMaxY;
        }   
     
        /// <summary>
        /// 构造XY平面内面片绕Y轴逆时针旋转某个角度后形成的旋转体的侧面Mesh（包含外侧面、上侧面和下侧面）
        /// </summary>
        /// <param name="oVertices"></param>
        /// <param name="dAngle"></param>
        /// <param name="nSegment"></param>
        /// <returns></returns>
        private MeshDraft RotateSide(List<Vector3> oVertices, float dAngle, int nSegment)
        {
            var draft = new MeshDraft { name = "Revolution surface" };
            if (Math.Abs(dAngle) <= 1)
            {
                return draft;
            }            
            
            float dSegmentAngle = 360f / nSegment;
            int nRingsCount = (int)Mathf.Floor(dAngle / dSegmentAngle) + 1;
            bool bClosedCircle = (dAngle >= 360);
            if (bClosedCircle)
            {
                //旋转360度的话，360度的旋转面和0度的旋转面重合
                //这个面只能保留一个，负责在会导致视觉上的错误（闭合处视觉不连贯）
                nRingsCount--;
            }
            
            //获取旋转后各个角度的面片的顶点序列
            float currentAngle = 90;
            int nPointCount = mEdgeLines.Count * 2;
            for (int i = 0; i < nRingsCount; i++)
            {                
                for (int j = 0; j < mEdgeLines.Count; j++)
                {
                    var oStartPoint = PTUtils.PointOnCircle3XZ(oVertices[mEdgeLines[j].start].x, currentAngle);
                    draft.vertices.Add(oStartPoint + Vector3.up * oVertices[mEdgeLines[j].start].y);
                    draft.uv.Add(new Vector2((float)i / nRingsCount, (float)j / mEdgeLines.Count));

                    var oEndPoint = PTUtils.PointOnCircle3XZ(oVertices[mEdgeLines[j].end].x, currentAngle);
                    draft.vertices.Add(oEndPoint + Vector3.up * oVertices[mEdgeLines[j].end].y);
                    draft.uv.Add(new Vector2((float)i / nRingsCount, (float) (2 * j + 1) / (2 * mEdgeLines.Count)));
                }

                currentAngle -= dSegmentAngle;
            } 
            
            //构造三角面片
            //相邻面片之间，每两组对应的边缘顶点可构成两个三角片面
            for (int i = 1; i < nRingsCount; i++)
            {
                int nLeftStartIndex = (i - 1) * nPointCount;
                int nRightStartIndex = i * nPointCount;
                for (int j = 0; j < mEdgeLines.Count; j++)
                {
                    Vector3 oNormal = Vector3.up.normalized;

                    if (!mPointOnAlignAxis.ContainsKey(mEdgeLines[j].start))
                    {
                        draft.triangles.Add(nRightStartIndex + 2 * j);
                        draft.triangles.Add(nLeftStartIndex + 2 * j + 1);
                        draft.triangles.Add(nLeftStartIndex + 2 * j);
                        oNormal = Vector3.Cross((draft.vertices[nLeftStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]),
                            (draft.vertices[nLeftStartIndex + 2 * j] - draft.vertices[nRightStartIndex + 2 * j]));
                        oNormal.Normalize();
                    }
                    if (!mPointOnAlignAxis.ContainsKey(mEdgeLines[j].end))
                    {
                        draft.triangles.Add(nRightStartIndex + 2 * j);
                        draft.triangles.Add(nRightStartIndex + 2 * j + 1);
                        draft.triangles.Add(nLeftStartIndex + 2 * j + 1);
                        oNormal = Vector3.Cross((draft.vertices[nRightStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]),
                            (draft.vertices[nLeftStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]));
                        oNormal.Normalize();
                    }
                    draft.normals.Add(oNormal);
                    draft.normals.Add(oNormal);
                }
            }

            //旋转一圈，构造第一个旋转面与最后一个旋转面之间的侧面
            if (bClosedCircle)
            {                
                int nLeftStartIndex = (nRingsCount - 1) * nPointCount;
                int nRightStartIndex = 0;
                for (int j = 0; j < mEdgeLines.Count; j++)
                {
                    Vector3 oNormal = Vector3.up.normalized;

                    if (!mPointOnAlignAxis.ContainsKey(mEdgeLines[j].start))
                    {
                        draft.triangles.Add(nRightStartIndex + 2 * j);
                        draft.triangles.Add(nLeftStartIndex + 2 * j + 1);
                        draft.triangles.Add(nLeftStartIndex + 2 * j);
                        oNormal = Vector3.Cross((draft.vertices[nLeftStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]),
                            (draft.vertices[nLeftStartIndex + 2 * j] - draft.vertices[nRightStartIndex + 2 * j]));
                        oNormal.Normalize();
                    }
                    if (!mPointOnAlignAxis.ContainsKey(mEdgeLines[j].end))
                    {
                        draft.triangles.Add(nRightStartIndex + 2 * j);
                        draft.triangles.Add(nRightStartIndex + 2 * j + 1);
                        draft.triangles.Add(nLeftStartIndex + 2 * j + 1);
                        oNormal = Vector3.Cross((draft.vertices[nRightStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]),
                            (draft.vertices[nLeftStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]));
                        oNormal.Normalize();
                    }
                    draft.normals.Add(oNormal);
                    draft.normals.Add(oNormal);
                }
            }
            //等分部分的旋转角度小于总旋转角度，需要构造剩余角度对应的旋转侧面
            else if ((nRingsCount - 1) * dSegmentAngle < dAngle)
            {
                for (int j = 0; j < mEdgeLines.Count; j++)
                {
                    var oStartPoint = PTUtils.PointOnCircle3XZ(oVertices[mEdgeLines[j].start].x, 90 - dAngle);
                    draft.vertices.Add(oStartPoint + Vector3.up * oVertices[mEdgeLines[j].start].y);
                    draft.uv.Add(new Vector2(1, 1));

                    var oEndPoint = PTUtils.PointOnCircle3XZ(oVertices[mEdgeLines[j].end].x, 90 - dAngle);
                    draft.vertices.Add(oEndPoint + Vector3.up * oVertices[mEdgeLines[j].end].y);
                    draft.uv.Add(new Vector2(1, 1));
                }

                int nLeftStartIndex = (nRingsCount - 1) * nPointCount;
                int nRightStartIndex = nRingsCount * nPointCount;
                for (int j = 0; j < mEdgeLines.Count; j++)
                {
                    Vector3 oNormal = Vector3.up.normalized;
                    if (!mPointOnAlignAxis.ContainsKey(mEdgeLines[j].start))
                    {
                        draft.triangles.Add(nRightStartIndex + 2 * j);
                        draft.triangles.Add(nLeftStartIndex + 2 * j + 1);
                        draft.triangles.Add(nLeftStartIndex + 2 * j);
                        oNormal = Vector3.Cross((draft.vertices[nLeftStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]),
                            (draft.vertices[nLeftStartIndex + 2 * j] - draft.vertices[nRightStartIndex + 2 * j]));
                        oNormal.Normalize();
                    }
                    if (!mPointOnAlignAxis.ContainsKey(mEdgeLines[j].end))
                    {
                        draft.triangles.Add(nRightStartIndex + 2 * j);
                        draft.triangles.Add(nRightStartIndex + 2 * j + 1);
                        draft.triangles.Add(nLeftStartIndex + 2 * j + 1);
                        oNormal = Vector3.Cross((draft.vertices[nRightStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]),
                            (draft.vertices[nLeftStartIndex + 2 * j + 1] - draft.vertices[nRightStartIndex + 2 * j]));
                        oNormal.Normalize();
                    }
                    draft.normals.Add(oNormal);
                    draft.normals.Add(oNormal);
                }                
            }

            if (!bClosedCircle)
            {
                int nStartIndex = draft.normals.Count - nPointCount;
                for (int i = 0; i < nPointCount; i++)
                {
                    draft.normals.Add(draft.normals[nStartIndex + i]);
                }
            }     

            return draft;
        }

        public void RotatedBodyByLeftLine(float dAngle, int nSegment)
        {
            dAngle = Mathf.Clamp(dAngle, 0, 360);
            if (mMesh == null)
            {
                throw new ArgumentNullException("mMesh");
            }
            if (mIsOrignalMesh)
            {
                AlignMesh(true);
            }
            MeshDraft oFrontMesh = new MeshDraft(mMesh);
            oFrontMesh.Move(-1 * mAlignPostion);
            InitPointOnAlignAxis(oFrontMesh.vertices);

            //构造旋转外侧面
            MeshDraft oSideMesh = RotateSide(oFrontMesh.vertices, Mathf.Abs(dAngle), nSegment);
            

            //构造旋转后的侧面
            if (dAngle < 360)
            {
                MeshDraft oBackDraft = new MeshDraft(mMesh);
                oBackDraft.Move(-1 * mAlignPostion);
                oBackDraft.Rotate(Quaternion.Euler(0, -1 * Mathf.Abs(dAngle), 0));
                oBackDraft.FlipFaces();


                oSideMesh.Add(oFrontMesh);
                oSideMesh.Add(oBackDraft);
            }
            oSideMesh.Move(mAlignPostion);

            GetComponent<MeshFilter>().mesh = oSideMesh.ToMesh();
        }
    }
}